home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Applications / UUCP / UUCon / Source / Subprocess.m < prev    next >
Text File  |  1993-01-31  |  9KB  |  420 lines

  1. /*----------------------------------------------------------------------------
  2.     Subprocess.m
  3.     
  4.     From Subprocess example by Charles L. Oei
  5.                                     pty support by Joe Freeman
  6.                                     with encouragement from Kristofer Younger
  7.                                     Subprocess Example, Release 2.0
  8.                                     NeXT Computer, Inc.
  9.     
  10.     You may freely copy, distribute and reuse the code in this example.
  11.     NeXT disclaims any warranty of any kind, expressed or implied, as to
  12.     its fitness for any particular use.
  13.         
  14.     SYNOPSIS
  15.         Handles a UNIX process that runs asynchronously.
  16.   
  17.     REVISIONS
  18.     Subprocess.m,v
  19. # Revision 1.4  1993/02/01  02:21:31  nwc
  20. # Added baud rate button. Cleaned interface. Fixed subprocess bug.
  21. #
  22. # Revision 1.3  1992/09/15  14:59:23  nwc
  23. # Made NeXTSTEP 3.0 compatible.
  24. # Added baud rate preference since that now works in uuq.
  25. #
  26. # Revision 1.2  1992/09/03  16:33:48  nwc
  27. # Clean up after Subprocesses.
  28. #
  29. # Revision 1.1.1.1  1992/08/18  14:34:20  nwc
  30. # GENESIS
  31. #
  32. # Revision 1.1  1992/07/04  03:17:22  nwc
  33. # Initial revision
  34. #
  35. ----------------------------------------------------------------------------*/
  36. #import <sys/wait.h>
  37. #import <sys/resource.h>
  38. #import <appkit/nextstd.h>
  39. #import <appkit/Application.h>
  40. #import <appkit/Panel.h>
  41. #import "Subprocess.h"
  42.  
  43. extern int  wait4(int, union wait *, int, struct rusage *);
  44. static void fdHandler(int theFd, id self);
  45. static void stderrFdHandler(int theFd, id self);
  46.  
  47. #define PIPE_ERROR            "Error starting UNIX pipes to subprocess."
  48. #define VFORK_ERROR            "Error starting UNIX vfork of subprocess."
  49.  
  50. @interface Subprocess(Private)
  51. - childDidExit;
  52. - fdHandler:(int)theFd;
  53. @end
  54.  
  55. @implementation Subprocess(Private)
  56.  
  57. /*
  58.  * cleanup after a child process exits
  59.  */
  60. - childDidExit
  61. {
  62.    union wait  w;
  63.    int         status = 0;
  64.    int         thePid;
  65.  
  66.    thePid = wait4(childPid, &w, WUNTRACED, NULL);
  67. #ifdef DEBUG
  68.    fprintf(stderr, "%s: wait4() on process id #%d returned %d, with "
  69.        "w_status = %d, w_retcode = %u, w_stopval = %u, "
  70.        "w_stopsig = %u, w_termsig = %d\n",
  71.        [self name], childPid, thePid, w.w_status, w.w_retcode,
  72.        w.w_stopval, w.w_stopsig, w.w_termsig);
  73. #endif
  74.    if (thePid > 0)
  75.    {
  76.       if (WIFEXITED(w))
  77.      status = (w.w_status >> 8);
  78.       else
  79.       {
  80.      if (WIFSTOPPED(w))
  81.         status = SUBPROCESS_STOPPED;
  82.      else
  83.      {
  84.         if (WIFSIGNALED(w))
  85.            status = SUBPROCESS_SIGNALED;
  86.      }
  87.       }
  88.       DPSRemoveFD(fromChild);
  89.       DPSRemoveFD(stderrFromChild);
  90.       fclose(fpFromChild);
  91.       close(fromChild);
  92.       close(stderrFromChild);
  93.       fclose(fpToChild);
  94.       running = NO;
  95.       [delegate perform:@selector(subprocess:done:)
  96.        with :self
  97.        with:(void *)status];
  98.       if(markedForFree)
  99.       [NXApp delayedFree: self];
  100.    }
  101.    return (self);
  102. }
  103.  
  104. /*
  105.  * DPS handler for output from subprocess
  106.  */
  107. - fdHandler:(int)theFd
  108. {
  109.    char       *s, *linep;
  110.    int         bufferCount;
  111.  
  112.    bufferCount = read(theFd, outputBuffer + outputBufferLen,
  113.               BUFSIZ - outputBufferLen);
  114.    if (bufferCount <= 0)
  115.    {
  116.       [self childDidExit];
  117.       return (self);
  118.    }
  119.    outputBuffer[bufferCount + outputBufferLen] = '\0';
  120.  
  121.    /*
  122.     * Send lines in the buffer to the delegate 
  123.     */
  124.    s = linep = outputBuffer;
  125.    while (s != NULL)
  126.    {
  127.       if ((s = index(linep, '\n')) != NULL)
  128.       {
  129.      *s = (char)0;
  130.      [delegate perform:@selector(subprocess:output:)
  131.       with :self
  132.       with:(void *)linep];
  133.      linep = s + 1;
  134.       }
  135.    }
  136.  
  137.    /*
  138.     * Copy the last part of the line back into the input buffer for next time (incomplete line) 
  139.     */
  140.    outputBufferLen = strlen(linep);
  141.    strncpy(outputBuffer, linep, outputBufferLen);
  142.  
  143.    return (self);
  144. }
  145.  
  146. @end
  147.  
  148. @implementation Subprocess
  149.  
  150. /*
  151.  * Cover for the init:withDelegate: with a nil delegate
  152.  */
  153. - init:(const char *)subprocessString
  154. {
  155.    return ([self init:subprocessString
  156.         withDelegate:nil]);
  157. }
  158.  
  159. - init:(const char *)subprocessString withDelegate:theDelegate
  160. {
  161.    int         pipeTo[2];
  162.    int         pipeFrom[2];
  163.    int         pipeStderr[2];            /* for stderr to different fd */
  164.    int         numFds, fd;
  165.    int processGroup;
  166.  
  167.    [super init];
  168.    markedForFree = NO;
  169.    outputBufferLen = 0;
  170.    stderrBufferLen = 0;
  171.    [self setDelegate:theDelegate];
  172.  
  173.    if (pipe(pipeTo) < 0 || pipe(pipeFrom) < 0 || pipe(pipeStderr) < 0)
  174.    {
  175.       [delegate perform:@selector(subprocess:error:)
  176.        with :self
  177.        with:(void *)PIPE_ERROR];
  178.       return (self);
  179.    }
  180.  
  181.    switch (childPid = vfork())
  182.    {
  183.       case -1:                    /* error */
  184.      [delegate perform:@selector(subprocess:error:)
  185.       with :self
  186.       with:(void *)VFORK_ERROR];
  187.      return (self);
  188.  
  189.       case 0:                    /* child */
  190.      dup2(pipeTo[0], 0);
  191.      dup2(pipeFrom[1], 1);            /* get stdout from process */
  192.      dup2(pipeStderr[1], 2);        /* get stderr here */
  193.      numFds = getdtablesize();
  194.      for (fd = 3; fd < numFds; fd++)
  195.         close(fd);
  196.  
  197.      processGroup = getpid();
  198.      ioctl(0, TIOCSPGRP, (char *)&processGroup);
  199.      setpgrp(0,processGroup);
  200.  
  201.      /*
  202.       * we exec a /bin/sh so that cmds are easier to specify for the user 
  203.       */
  204.      execl("/bin/sh", "sh", "-c", subprocessString, 0);
  205.      perror("vfork (child)");        /* should never gets here tho */
  206.      exit(1);
  207.  
  208.       default:                    /* parent */
  209.      running = YES;
  210.  
  211.      close(pipeTo[0]);
  212.      close(pipeFrom[1]);
  213.      close(pipeStderr[1]);
  214.  
  215.      fpToChild = fdopen(pipeTo[1], "w");
  216.      fromChild = pipeFrom[0];
  217.      fpFromChild = fdopen(pipeFrom[0], "r");
  218.  
  219.      stderrFromChild = pipeStderr[0];
  220.  
  221.      /*
  222.       * Set buffering method, also make it use its own buffers 
  223.       */
  224.      setbuf(fpToChild, NULL);        /* no buffering */
  225.      setbuf(fpFromChild, NULL);
  226.      DPSAddFD(fromChild, (DPSFDProc) fdHandler, (id) self,
  227.           NX_MODALRESPTHRESHOLD + 1);
  228.  
  229.      DPSAddFD(stderrFromChild, (DPSFDProc) stderrFdHandler, (id) self,
  230.           NX_MODALRESPTHRESHOLD + 1);
  231.  
  232.      return (self);
  233.    }
  234. }
  235.  
  236.  
  237. - stderrFdHandler:(int)theFd
  238. {
  239.    char       *s, *linep;
  240.    int         bufferCount;
  241.  
  242.    bufferCount = read(theFd, stderrBuffer + stderrBufferLen,
  243.               BUFSIZ - stderrBufferLen);
  244.    if (bufferCount <= 0)
  245.       return (self);
  246.  
  247.    stderrBuffer[bufferCount + stderrBufferLen] = '\0';
  248.  
  249.    /*
  250.     * Send lines in the buffer to the delegate 
  251.     */
  252.    s = linep = stderrBuffer;
  253.    while (s != NULL)
  254.    {
  255.       if ((s = index(linep, '\n')) != NULL)
  256.       {
  257.      *s = (char)0;
  258.      [delegate perform:@selector(subprocess:stderrOutput:)
  259.       with :self
  260.       with:(void *)linep];
  261.      linep = s + 1;
  262.       }
  263.    }
  264.  
  265.    /*
  266.     * Copy the last part of the line back into the input buffer for next time (incomplete line) 
  267.     */
  268.    stderrBufferLen = strlen(linep);
  269.    strncpy(stderrBuffer, linep, stderrBufferLen);
  270.  
  271.    return (self);
  272. }
  273.  
  274. - send:(const char *)string withNewline:(BOOL) wantNewline
  275. {
  276.    fputs(string, fpToChild);
  277.    if (wantNewline)
  278.       fputc('\n', fpToChild);
  279.    return (self);
  280. }
  281.  
  282. - send:(const char *)string
  283. {
  284.    [self send:string withNewline:YES];
  285.    return (self);
  286. }
  287.  
  288. /*
  289.  * Returns the process id of the process (and therefore the process group
  290.  * of the job)
  291.  */
  292. - (int)pid
  293. {
  294.    return (childPid);
  295. }
  296.  
  297. - (BOOL) isPaused
  298. {
  299.    return (paused);
  300. }
  301.  
  302. - resume:sender
  303. {
  304.    if (paused)
  305.    {
  306.       killpg(childPid, SIGCONT);        /* resume the process group */
  307.       paused = NO;
  308.    }
  309.    return (self);
  310. }
  311.  
  312. - pause:sender
  313. {
  314.    if (!paused)
  315.    {
  316.       killpg(childPid, SIGSTOP);        /* pause the process group */
  317.       paused = YES;
  318.    }
  319.    return (self);
  320. }
  321.  
  322. - (BOOL) isRunning
  323. {
  324.    return (running);
  325. }
  326.  
  327. - terminate:sender
  328. {
  329.    if (running)
  330.       killpg(childPid, SIGKILL);
  331.    return (self);
  332. }
  333.  
  334. - free
  335. {
  336.    if(running && !markedForFree)
  337.    {
  338.       markedForFree = YES;
  339.       return [self terminate: self];
  340.    }
  341.    else
  342.        return [super free];
  343. }
  344.  
  345. /*
  346.  * effectively sends an EOF to the child process stdin
  347.  */
  348. - terminateInput
  349. {
  350.    fclose(fpToChild);
  351.    return (self);
  352. }
  353.  
  354. - setDelegate:anObject
  355. {
  356.    delegate = anObject;
  357.    return (self);
  358. }
  359.  
  360. - delegate
  361. {
  362.    return (delegate);
  363. }
  364.  
  365. @end
  366.  
  367. @implementation Object(SubprocessDelegate)
  368.  
  369. - subprocess: sender done:(int)exitStatus
  370. {
  371.    return (self);
  372. }
  373.  
  374. - subprocess:sender output:(char *)buffer
  375. {
  376.    return (self);
  377. }
  378.  
  379. - subprocess:sender stderrOutput:(char *)buffer
  380. {
  381.    return (self);
  382. }
  383.  
  384. - subprocess:sender error:(const char *)errorString
  385. {
  386.    if (NXApp)
  387.       NXRunAlertPanel(0, errorString, 0, 0, 0);
  388.    else
  389.       perror(errorString);
  390.    return (self);
  391. }
  392.  
  393. @end
  394.  
  395.  
  396. typedef struct
  397. {
  398.    @defs(Object)
  399. } object;
  400.  
  401. /*
  402.  * And standard error from subprocess
  403.  */
  404. static void stderrFdHandler(int theFd, id self)
  405. {
  406.    if(self && ((object *)self)->isa)
  407.        [self stderrFdHandler:theFd];
  408. }
  409.  
  410. /*
  411.  * DPS handler for output from subprocess
  412.  */
  413. static void fdHandler(int theFd, id self)
  414. {
  415.    if(self && ((object *)self)->isa)
  416.        [self fdHandler:theFd];
  417. }
  418.  
  419.  
  420.